#include <cstring>
#include <fstream>
#include <limits>
#include <TriangulatedMeshes.h>

using namespace cagd;
using namespace std;

TriangulatedMesh::TriangulatedMesh(GLuint vertex_count, GLuint face_count, GLenum usage_flag)
{
    _usage_flag = usage_flag;
    _vbo_vertices = _vbo_normals = _vbo_tex_coordinates = _vbo_indices = 0;
    _vertex.resize(vertex_count);
    _normal.resize(vertex_count);
    _tex.resize(vertex_count);
    _face.resize(face_count);
}

TriangulatedMesh::TriangulatedMesh(const TriangulatedMesh &mesh):
        _usage_flag(mesh._usage_flag),
        _vbo_vertices(0), _vbo_normals(0), _vbo_tex_coordinates(0), _vbo_indices(0),
        _vertex(mesh._vertex),
        _normal(mesh._normal),
        _tex(mesh._tex),
        _face(mesh._face)
{
    if (mesh._vbo_indices && mesh._vbo_normals && mesh._vbo_tex_coordinates && mesh._vbo_vertices)
        UpdateVertexBufferObjects(mesh._usage_flag);
}

TriangulatedMesh& TriangulatedMesh::operator =(const TriangulatedMesh& rhs)
{
    if (this == &rhs)
        return *this;

    DeleteVertexBufferObjects();
    _usage_flag = rhs._usage_flag;
    _vertex = rhs._vertex;
    _normal = rhs._normal;
    _tex = rhs._tex;
    _face = rhs._face;

    if (rhs._vbo_indices && rhs._vbo_normals && rhs._vbo_tex_coordinates && rhs._vbo_vertices)
        UpdateVertexBufferObjects(rhs._usage_flag);

    return *this;
}

GLvoid TriangulatedMesh::DeleteVertexBufferObjects()
{
    if (_vbo_vertices)
    {
        glDeleteBuffersARB(1, &_vbo_vertices);
        _vbo_vertices = 0;
    }
    if (_vbo_normals)
    {
        glDeleteBuffersARB(1, &_vbo_normals);
        _vbo_normals = 0;
    }
    if (_vbo_tex_coordinates)
    {
        glDeleteBuffersARB(1, &_vbo_tex_coordinates);
        _vbo_tex_coordinates = 0;
    }
    if (_vbo_indices)
    {
        glDeleteBuffersARB(1, &_vbo_indices);
        _vbo_indices = 0;
    }
}

GLboolean TriangulatedMesh::Render(GLenum render_mode) const
{
    if (!_vbo_indices || !_vbo_normals || !_vbo_tex_coordinates || !_vbo_vertices)
        return false;
    if (render_mode != GL_TRIANGLES && render_mode != GL_POINTS)
        return false;

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_tex_coordinates);
    glTexCoordPointer(4, GL_FLOAT, 0, (const GLvoid*)0);

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_normals);
    glNormalPointer(GL_FLOAT, 0, (const GLvoid*)0);

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_vertices);
    glVertexPointer(3, GL_FLOAT, 0, (const GLvoid*)0);

    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, _vbo_indices);
    glDrawElements(render_mode, 3 * _face.size(), GL_UNSIGNED_INT, (const GLvoid*)0);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER, 0);
    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0);

    return true;
}

GLboolean TriangulatedMesh::UpdateVertexBufferObjects(GLenum usage_flag)
{
    if (usage_flag != GL_STREAM_DRAW && usage_flag != GL_STREAM_READ && usage_flag != GL_STREAM_COPY &&
        usage_flag != GL_STATIC_DRAW && usage_flag != GL_STATIC_READ && usage_flag != GL_STATIC_COPY &&
        usage_flag != GL_DYNAMIC_DRAW && usage_flag != GL_DYNAMIC_READ && usage_flag != GL_DYNAMIC_COPY)
        return false;

    _usage_flag = usage_flag;

    DeleteVertexBufferObjects();

    glGenBuffersARB(1, &_vbo_vertices);
    if (!_vbo_vertices)
        return false;

    glGenBuffersARB(1, &_vbo_normals);
    if (!_vbo_normals)
    {
        glDeleteBuffersARB(1, &_vbo_vertices);
        _vbo_vertices = 0;
        return false;
    }

    glGenBuffersARB(1, &_vbo_tex_coordinates);
    if (!_vbo_tex_coordinates)
    {
        glDeleteBuffersARB(1, &_vbo_vertices);
        _vbo_vertices = 0;
        glDeleteBuffersARB(1, &_vbo_normals);
        _vbo_normals = 0;
        return false;
    }

    glGenBuffersARB(1, &_vbo_indices);
    if (!_vbo_indices)
    {
        glDeleteBuffersARB(1, &_vbo_vertices);
        _vbo_vertices = 0;
        glDeleteBuffersARB(1, &_vbo_normals);
        _vbo_normals = 0;
        glDeleteBuffersARB(1, &_vbo_tex_coordinates);
        _vbo_tex_coordinates = 0;
        return false;
    }

    GLuint vertex_byte_size = 3 * _vertex.size() * sizeof(float);

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_vertices);
    glBufferDataARB(GL_ARRAY_BUFFER, vertex_byte_size, 0, usage_flag);

    GLfloat *vertex_coordinate = (GLfloat*)glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_normals);
    glBufferDataARB(GL_ARRAY_BUFFER, vertex_byte_size, 0, usage_flag);

    GLfloat *normal_coordinate = (GLfloat*)glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

    for (vector<DCoordinate>::const_iterator vit = _vertex.begin(), nit = _normal.begin(); vit != _vertex.end(); vit++, nit++)
    {
        for (GLint component = 0; component < 3; component++)
        {
            *vertex_coordinate = (GLfloat)(*vit)[component];
            vertex_coordinate++;

            *normal_coordinate = (GLfloat)(*nit)[component];
            normal_coordinate++;
        }
    }

    GLuint tex_byte_size = 4 * _tex.size() * sizeof(GLfloat);

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_tex_coordinates);
    glBufferDataARB(GL_ARRAY_BUFFER, tex_byte_size, 0, _usage_flag);
    GLfloat *tex_coordinate = (GLfloat*)glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

    memcpy(tex_coordinate, &_tex[0][0], tex_byte_size);

    GLuint index_byte_size = 3 * _face.size() * sizeof(GLuint);

    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, _vbo_indices);
    glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, index_byte_size, 0, _usage_flag);
    GLuint *element = (GLuint*)glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);

    for (vector<TriangularFace>::const_iterator fit = _face.begin(); fit != _face.end(); fit++)
    {
        for (GLint node = 0; node < 3; node++)
        {
            *element = (*fit)[node];
            ++element;
        }
    }

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_vertices);
    if (!glUnmapBufferARB(GL_ARRAY_BUFFER))
        return false;

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_normals);
    if (!glUnmapBufferARB(GL_ARRAY_BUFFER))
        return false;

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_tex_coordinates);
    if (!glUnmapBufferARB(GL_ARRAY_BUFFER))
        return false;

    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, _vbo_indices);
    if (!glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER))
        return false;

    glBindBufferARB(GL_ARRAY_BUFFER, 0);
    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0);

    return true;
}

GLboolean TriangulatedMesh::LoadFromOFF(const std::string &file_name, GLboolean translate_and_scale_to_unit_cube)
{
    fstream f(file_name.c_str(), ios_base::in);

    if (!f.good())
        return false;

    string header;

    f >> header;

    if (header != "OFF")
        return false;

    GLuint vertex_count, face_count, edge_count;

    f >> vertex_count >> face_count >> edge_count;

    _vertex.resize(vertex_count);
    _normal.resize(vertex_count);
    _tex.resize(vertex_count);
    _face.resize(face_count);

    _leftmost_vertex.x = _leftmost_vertex.y = _leftmost_vertex.z = numeric_limits<GLdouble>::max();
    _rightmost_vertex.x = _rightmost_vertex.y = _rightmost_vertex.z = -numeric_limits<GLdouble>::max();

    for (vector<DCoordinate>::iterator vit = _vertex.begin(); vit != _vertex.end(); vit++)
    {
        f >> *vit;

        if (vit->x < _leftmost_vertex.x)
            _leftmost_vertex.x = vit->x;
        if (vit->y < _leftmost_vertex.y)
            _leftmost_vertex.y = vit->y;
        if (vit->z < _leftmost_vertex.z)
            _leftmost_vertex.z = vit->z;

        if (vit->x > _rightmost_vertex.x)
            _rightmost_vertex.x = vit->x;
        if (vit->y > _rightmost_vertex.y)
            _rightmost_vertex.y = vit->y;
        if (vit->z > _rightmost_vertex.z)
            _rightmost_vertex.z = vit->z;
    }

    if (translate_and_scale_to_unit_cube)
    {
        GLdouble scale = 1.0 / max(_rightmost_vertex.x - _leftmost_vertex.x,
                                   max(_rightmost_vertex.y - _leftmost_vertex.y,
                                       _rightmost_vertex.z - _leftmost_vertex.z));
        DCoordinate middle(_leftmost_vertex);
        middle += _rightmost_vertex;
        middle *= 0.5;
        for (vector<DCoordinate>::iterator vit = _vertex.begin(); vit != _vertex.end(); vit++)
        {
            *vit -= middle;
            *vit *= scale;
        }
    }

    for (vector<TriangularFace>::iterator fit = _face.begin(); fit != _face.end(); fit++)
        f >> *fit;

    for (vector<TriangularFace>::const_iterator fit = _face.begin(); fit != _face.end(); fit++)
    {
        DCoordinate n = _vertex[(*fit)[1]];
        n -= _vertex[(*fit)[0]];

        DCoordinate p = _vertex[(*fit)[2]];
        p -= _vertex[(*fit)[0]];

        n %= p;

        for (GLint node = 0; node < 3; node++)
            _normal[(*fit)[node]] += n;
    }

    for (vector<DCoordinate>::iterator nit = _normal.begin(); nit != _normal.end(); nit++)
        nit->Normalize();

    f.close();

    return true;
}

GLfloat* TriangulatedMesh::MapVertexBuffer(GLenum access_flag) const
{
    if (access_flag != GL_READ_ONLY && access_flag != GL_WRITE_ONLY && access_flag != GL_READ_WRITE)
        return (GLfloat*)0;

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_vertices);
    GLfloat* result = (GLfloat*)glMapBufferARB(GL_ARRAY_BUFFER, access_flag);
    glBindBufferARB(GL_ARRAY_BUFFER, 0);

    return result;
}

GLvoid TriangulatedMesh::UnmapVertexBuffer() const
{
    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_vertices);
    glUnmapBufferARB(GL_ARRAY_BUFFER);
    glBindBufferARB(GL_ARRAY_BUFFER, 0);
}

GLfloat* TriangulatedMesh::MapNormalBuffer(GLenum access_flag) const
{
    if (access_flag != GL_READ_ONLY && access_flag != GL_WRITE_ONLY && access_flag != GL_READ_WRITE)
        return (GLfloat*)0;

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_normals);
    GLfloat* result = (GLfloat*)glMapBufferARB(GL_ARRAY_BUFFER, access_flag);
    glBindBufferARB(GL_ARRAY_BUFFER, 0);

    return result;
}

GLvoid TriangulatedMesh::UnmapNormalBuffer() const
{
    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_normals);
    glUnmapBufferARB(GL_ARRAY_BUFFER);
    glBindBufferARB(GL_ARRAY_BUFFER, 0);
}

GLfloat* TriangulatedMesh::MapTextureBuffer(GLenum access_flag) const
{
    if (access_flag != GL_READ_ONLY && access_flag != GL_WRITE_ONLY && access_flag != GL_READ_WRITE)
        return (GLfloat*)0;

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_tex_coordinates);
    GLfloat* result = (GLfloat*)glMapBufferARB(GL_ARRAY_BUFFER, access_flag);
    glBindBufferARB(GL_ARRAY_BUFFER, 0);

    return result;
}

GLvoid TriangulatedMesh::UnmapTextureBuffer() const
{
    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_tex_coordinates);
    glUnmapBufferARB(GL_ARRAY_BUFFER);
    glBindBufferARB(GL_ARRAY_BUFFER, 0);
}

TriangulatedMesh::~TriangulatedMesh()
{
    DeleteVertexBufferObjects();
}

GLuint TriangulatedMesh::VertexCount() const
{
    return _vertex.size();
}
